package yartiss.framework.viewmodel;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import yartiss.framework.view.IView;

public abstract class AbstractViewModel implements IPropertyChangedNotify{
	private final Map<String, Method> properties;
	private final Map<String, Method> setProperties;
	
	protected AbstractViewModel(){
		properties 	  = getProperties(this.getClass());
		setProperties = getSetProperties(this.getClass());
	}
	
	private static Map<String, Method> getProperties(Class<? extends AbstractViewModel> clazz) {
		Map<String, Method> props = new HashMap<String, Method>();
		for (Method method : clazz.getMethods()) {
			if(method.getName().startsWith("get")){
				String propName = method.getName().substring(3).toLowerCase();
				props.put(propName, method);
			}
		}
		return props;
	}
	
	private static Map<String, Method> getSetProperties(Class<? extends AbstractViewModel> clazz) {
		Map<String, Method> props = new HashMap<String, Method>();
		for (Method method : clazz.getMethods()) {
			
			if(method.getName().startsWith("set")){
				String propName = method.getName().substring(3).toLowerCase();
				props.put(propName, method);
			}
		}
		return props;
	}

	@SuppressWarnings("unchecked")
	public <T> T getProperty(String propertyName){
		propertyName = propertyName.toLowerCase();
		try {
			Method method = (properties.containsKey(propertyName) ? properties.get(propertyName) : null);
			if(method != null){
				T value = (T) method.invoke(this);
				return value;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public <T> void setProperty(String propertyName, T value){
		propertyName = propertyName.toLowerCase();
		

		try {
			Method method = (setProperties.containsKey(propertyName) ? setProperties.get(propertyName) : null);
			
			if(method != null){
				Class<?> type = method.getParameterTypes()[0];
				if(type == Integer.class || type == int.class){
					int val = value == null ? 0 : Integer.parseInt(value.toString());
					method.invoke(this, val);
				}
				else if(type == Long.class || type == long.class){
					long val = value == null ? 0l :Long.parseLong(value.toString());
					method.invoke(this, val);
				}
				else if(type == Double.class || type == double.class){
					double val =  value == null ? 0 :Double.parseDouble(value.toString());
					method.invoke(this, val);
				}
				else if(type == String.class){
					String val =  value ==null ? "" : value.toString();
					method.invoke(this, val);
				}
				else if(type == Boolean.class || type == boolean.class){
					boolean val =  value ==null ? false : Boolean.parseBoolean(value.toString());
					method.invoke(this, val);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private Map<String,List<PropertyChangeListener>> listenersByProperty = new HashMap<String, List<PropertyChangeListener>>();
	private List<PropertyChangeListener> listeners = new LinkedList<PropertyChangeListener>();
	
	@Override
	public void registerPropertyChangeListerner(PropertyChangeListener listener) {
		listeners.add(listener);
	}
	
	@Override
	public void registerPropertyChangeListerner(String propertyName, PropertyChangeListener listener) {
		List<PropertyChangeListener> ls = (listenersByProperty.containsKey(propertyName) ? listenersByProperty.get(propertyName) : null);
		if(ls == null) ls = new LinkedList<PropertyChangeListener>();
		ls.add(listener);
		listenersByProperty.put(propertyName, ls);
	}

	@Override
	public void unregisterPropertyChangeListener(PropertyChangeListener listener) {
		if(listeners.contains(listener))
			listeners.remove(listener);
		if(listenersByProperty.containsValue(listener)){
			//TODO
		}
	}
	
	protected void firePropertyChanged(String propertyName, Object oldValue, Object newValue ){
		for (PropertyChangeListener l : listeners) {
			l.propertyChange(new PropertyChangeEvent(this, propertyName, oldValue, newValue));
		}
		if(listenersByProperty.containsKey(propertyName)){
			for (PropertyChangeListener l : listenersByProperty.get(propertyName)) {
				l.propertyChange(new PropertyChangeEvent(this, propertyName, oldValue, newValue));
			}
		}
	}
	
	public abstract IView getView();

}
